package com.xuebusi.toutiao.admin.api.common.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.TypeReference;
import com.xuebusi.toutiao.admin.api.common.enums.TokenEnum;
import com.xuebusi.toutiao.admin.api.common.exception.CustomException;
import com.xuebusi.toutiao.admin.api.common.result.JwtToken;
import com.xuebusi.toutiao.admin.api.user.entity.UserEntity;
import io.jsonwebtoken.*;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;

public class JwtUtil {
    // JWT 令牌秘钥（盐）
    private static final String JWT_KEY = "c27a4e22ac03489b8b44532c66a90693";

    // token在2小时后失效
    public static final int TOKEN_EXPIRE_MILLIS = 2 * 3600 * 1000;

    // refresh_token在14天后失效
    public static final int REFRESH_TOKEN_EXPIRE_MILLIS = 14 * 24 * 3600 * 1000;

    /**
     * 生成token令牌
     *
     * @param userEntity 用户信息
     * @param tokenEnum  令牌类型枚举
     * @return
     */
    public static String generalToken(UserEntity userEntity, TokenEnum tokenEnum) {
        // 主题信息
        Map<String, Object> subjectMap = new LinkedHashMap<>();
        subjectMap.put("user", userEntity);
        subjectMap.put("type", tokenEnum.toString());
        String subject = JSON.toJSONString(subjectMap);
        // 令牌唯一标识
        String id = UUID.randomUUID().toString().replace("-", "");
        // 令牌签发者
        String issuer = "xuebusi";
        long tokenExpireMillis = tokenEnum == TokenEnum.TOKEN ? TOKEN_EXPIRE_MILLIS : REFRESH_TOKEN_EXPIRE_MILLIS;
        String token = JwtUtil.createJWT(id, issuer, subject, tokenExpireMillis);
        return token;
    }

    /**
     * 解析token令牌
     *
     * @param token
     * @return
     */
    public static JwtToken<UserEntity> parseJwtToken(String token) {
        Claims claims = parseJWT(token);
        String subject = claims.getSubject();
        JwtToken<UserEntity> jwtToken;
        try {
            jwtToken = JSON.parseObject(subject, new TypeReference<JwtToken<UserEntity>>() {
            });
        } catch (JSONException exception) {
            throw new CustomException("token解析异常", 401);
        }
        return jwtToken;
    }

    /**
     * 从token中解析用户信息
     *
     * @param token
     * @return
     */
    public static UserEntity resolveUserInfo(String token) {
        JwtToken<UserEntity> jwtToken = JwtUtil.parseJwtToken(token);
        return jwtToken.getUser();
    }

    /**
     * 创建jwt
     *
     * @param id        设置jti(JWT ID)：是JWT的唯一标识，根据业务需要，
     *                  这个可以设置为一个不重复的值，主要用来作为一次性token,
     *                  从而回避重放攻击。
     * @param issuer    issuer：jwt签发人
     * @param subject   代表这个JWT的主体，即它的所有人，这个是一个json格式的字符串，
     *                  可以存放什么userid，roldid之类的，作为什么用户的唯一标志。
     * @param ttlMillis
     * @return
     */
    private static String createJWT(String id, String issuer, String subject, long ttlMillis) {
        // 指定签名的时候使用的签名算法，也就是header那部分，jjwt已经将这部分内容封装好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        // 生成签名的时候使用的秘钥secret，切记这个秘钥不能外露哦。
        // 它就是你服务端的私钥，在任何场景都不应该流露出去。
        // 一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        SecretKey key = generalKey();

        JwtBuilder builder;
        try {
            builder = Jwts.builder()
                    .setId(id)
                    .setIssuedAt(now)
                    .setIssuer(issuer)
                    .setSubject(subject)
                    .signWith(signatureAlgorithm, key);
        } catch (ExpiredJwtException e) {
            throw new CustomException("token已过期", 401);
        } catch (UnsupportedJwtException e) {
            throw new CustomException("不支持该token类型", 401);
        } catch (MalformedJwtException e) {
            throw new CustomException("token被篡改", 401);
        } catch (SignatureException e) {
            throw new CustomException("token签名有误", 401);
        } catch (IllegalArgumentException e) {
            throw new CustomException("token参数非法", 401);
        }

        // 设置过期时间
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);
        }
        return builder.compact();
    }

    /**
     * 解密jwt
     *
     * @param token
     * @return
     * @throws Exception
     */
    private static Claims parseJWT(String token) {
        //签名秘钥，和生成的签名的秘钥一模一样
        SecretKey key = generalKey();
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(key)
                    .parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            throw new CustomException("token已过期", 401);
        } catch (UnsupportedJwtException e) {
            throw new CustomException("不支持该token类型", 401);
        } catch (MalformedJwtException e) {
            throw new CustomException("token被篡改", 401);
        } catch (SignatureException e) {
            throw new CustomException("token签名有误", 401);
        } catch (IllegalArgumentException e) {
            throw new CustomException("token参数非法", 401);
        }
        return claims;
    }

    /**
     * 由字符串生成加密key
     *
     * @return
     */
    private static SecretKey generalKey() {
        String stringKey = JWT_KEY;
        // 本地的密码解码
        byte[] encodedKey = Base64.getEncoder().encode(stringKey.getBytes());
        // 根据给定的字节数组使用AES加密算法构造一个密钥
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    /**
     * 生成令牌测试非法
     *
     * @param args
     */
    public static void main(String[] args) {
        Map<String, Object> user = new HashMap<>();
        user.put("id", "9");
        user.put("mobile", "13999999999");
        user.put("name", "zz2");
        user.put("photo", "/2021/03/04/625835fff54241199fa86400e05a3475.JPG");
        user.put("intro", "个人简介2");
        user.put("email", "zz2@126.com");
        String subject = JSON.toJSONString(user);

        try {
            JwtUtil util = new JwtUtil();
            // 令牌在60秒之后过期
            long jwtExpireMillis = System.currentTimeMillis() + 60 * 60 * 1000;
            String jwt = util.createJWT(UUID.randomUUID().toString(), "xuebusi", subject, jwtExpireMillis);
            System.out.println(jwt);

            System.out.println("---------------解密--------------------");

            Claims c = util.parseJWT(jwt);
            System.out.println(c.getSubject());
            System.out.println(c.getExpiration());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
